/*
 * 16 bit VESA service
 */

/*
   Copyright 2007 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <ktypes.h>
#include <vbe.h>
#include <mem.h>
#include <syscall.h>

void putc_xy(int x, int y, char c);

void resetES(void);

// return values for AL
#define VBE_FUNCTION_SUPPORTED 0x4f

// return values for AH
#define VBE_SUCCESS		0x00
#define VBE_FAILED		0x01
#define VBE_UNSUPPORTED	0x02
#define VBE_INVALID		0x03

#define APP_ERROR		0xff

typedef struct {
	uint8	signature[4];
	uint16	version;
	fp_t	oem_string_ptr;
	uint32	capabilities;
	fp_t	video_mode_ptr;
	uint16	total_memory;
	uint16	oem_software_rev;
	fp_t	oem_vendor_name_ptr;
	fp_t	oem_product_name_ptr;
	fp_t	oem_product_rev_ptr;
	uint16	reserved[111];
	uint8	oem_data[256];
} vbe_info_t;

/*
 * Message format
 */
typedef struct {
	uint8 function;
	uint8 parameter[512];
} message_t;

static message_t message;
static message_t response;
static int segment;

int getControllerInfo(vbe_info_t *info);

int vbeControllerInfo(void *buffer);
int vbeModeInfo(int mode, void *buffer);
int vbeSetMode(int mode, void *buffer);
int vbeDccInfo(int fn, int controller, int block, void *buffer);
int vbeDiplayStart(int fn, long param0, long param1, void *buffer);

int main(void) {
	handle_t pipe;
	pipe = CREATE_PIPE();
	
	while (MESSAGE_RECEIVE(pipe, (long) LINEAR(&message),
		(long) sizeof(message_t)) != -1) {
		
		long msgLen = 1;
		switch (message.function) {
			case 0x00: {	/* controller info */
				int resp = getControllerInfo((vbe_info_t *) response.parameter);
				response.function = resp >> 8;
				msgLen = sizeof(message_t);
			}
			break;
			
			case 0x01: {	/* mode info */
				int resp = vbeModeInfo(*(int *) message.parameter,
					response.parameter);
				response.function = resp >> 8;
				msgLen = 256 + 1;
				putc_xy(79, 5, 'M');
			}
			break;
			
			case 0x02: {	/* set mode */
				int resp = vbeSetMode(*(int *) message.parameter,
					*((int *) message.parameter + 1));
				response.function = resp >> 8;
				msgLen = 1;
				putc_xy(79, 6, 'S');
			}
			break;
			
			case 0x03: /* get mode */
				break;
			
			case 0x04: /* save/restore state */
				break;
			
			case 0x05: /* window control */
				break;
			
			case 0x07: {	/* get/set display start */
				int resp = vbeDisplayStart(*(char *) message.parameter,
					*((long *) (message.parameter + 1)),
					*((long *) (message.parameter + 5)), response.parameter);
				response.function = resp >> 8;
				msgLen = 5 + 1;
			}
			break;
			
			case 0x15: {	/* DCC extension */
				int resp = vbeDccInfo(*(int *) message.parameter,
					*((int *) message.parameter + 1),
					*((int *) message.parameter + 2), response.parameter);
				response.function = resp >> 8;
				msgLen = 128 + 1;
				putc_xy(79, 7, 'D');
			}
			break;
			
			default:
				response.function = VBE_UNSUPPORTED;
				break;
		}
		
		MESSAGE_REPLY(pipe, (long) LINEAR(&response), (long) msgLen);
	}
	
	DESTROY_PIPE(pipe);
	return 0;
}

int getControllerInfo(vbe_info_t *info) {
	uint16 resp = vbeControllerInfo(info);
	uint linear;
	uint oemIdx = 0, reservedIdx = 0;
	
	if (resp == (VBE_SUCCESS<<8) | VBE_FUNCTION_SUPPORTED) {
		putc_xy(79, 9, info->signature[0]);

		linear = FP_TO_LINEAR(info->video_mode_ptr);
		if (linear >= LINEAR(info->reserved)
			&& linear < (LINEAR(info->reserved) + 256)) {
			putc_xy(79, 8, 'I');
			
			/* the list is inside the reserved area, convert to an index */
			(uint) info->video_mode_ptr =
				linear - LINEAR(info->reserved);
		} else {
			uint16 tmp;
			fp_t base = info->video_mode_ptr;
			*(uint*) &info->video_mode_ptr = reservedIdx;
			
			putc_xy(79, 8, 'E');
			
			/* the list is external and needs to be copied in */
			do {
				tmp = farreadw(base.segment, base.offset);
				info->reserved[reservedIdx] = tmp;
				reservedIdx++;
				base.offset += 2;
			} while (tmp != 0xffff && reservedIdx < 111);
		}
		
		linear = FP_TO_LINEAR(info->oem_string_ptr);
		if (linear >= LINEAR(info->oem_data)
			&& linear < (LINEAR(info->oem_data) + 256)) {
			putc_xy(79, 8, 'I');
			
			/* the string is inside the OEM data area, convert to an index */
			*(uint*) &info->oem_string_ptr = linear - LINEAR(info->oem_data);
		} else {
			uint8 tmp;
			fp_t base = info->oem_string_ptr;
			*(uint*) &info->oem_string_ptr = oemIdx;
			
			putc_xy(79, 8, 'E');
			
			/* the string is external and needs to be copied in */
			do {
				tmp = farreadb(base.segment, base.offset);
				info->oem_data[oemIdx] = tmp;
				oemIdx++;
				base.offset++;
			} while (tmp);
		}
		
		linear = FP_TO_LINEAR(info->oem_vendor_name_ptr);
		if (linear >= LINEAR(info->oem_data)
			&& linear < (LINEAR(info->oem_data) + 256)) {
			putc_xy(79, 8, 'I');
			
			/* the string is inside the OEM data area, convert to an index */
			*(uint*) &info->oem_vendor_name_ptr =
				linear - LINEAR(info->oem_data);
		} else {
			uint8 tmp;
			fp_t base = info->oem_vendor_name_ptr;
			*(uint*) &info->oem_vendor_name_ptr = oemIdx;
			
			putc_xy(79, 8, 'E');
			
			/* the string is external and needs to be copied in */
			do {
				tmp = farreadb(base.segment, base.offset);
				info->oem_data[oemIdx] = tmp;
				oemIdx++;
				base.offset++;
			} while (tmp);
		}
		
		linear = FP_TO_LINEAR(info->oem_product_name_ptr);
		if (linear > LINEAR(info->oem_data)
			&& linear <= (LINEAR(info->oem_data) + 256)) {
			putc_xy(79, 8, 'I');
			
			/* the string is inside the OEM data area, convert to an index */
			*(uint*) &info->oem_product_name_ptr =
				linear - LINEAR(info->oem_data);
		} else {
			uint8 tmp;
			fp_t base = info->oem_product_name_ptr;
			*(uint*) &info->oem_product_name_ptr = oemIdx;
			
			putc_xy(79, 8, 'E');
			
			/* the string is external and needs to be copied in */
			do {
				tmp = farreadb(base.segment, base.offset);
				info->oem_data[oemIdx] = tmp;
				oemIdx++;
				base.offset++;
			} while (tmp);
		}
		
		linear = FP_TO_LINEAR(info->oem_product_rev_ptr);
		if (linear > LINEAR(info->oem_data)
			&& linear <= (LINEAR(info->oem_data) + 256)) {
			putc_xy(79, 8, 'I');
			
			/* the string is inside the OEM data area, convert to an index */
			*(uint*) &info->oem_product_rev_ptr =
				linear - LINEAR(info->oem_data);
		} else {
			uint8 tmp;
			fp_t base = info->oem_product_rev_ptr;
			*(uint*) &info->oem_product_rev_ptr = oemIdx;
			
			putc_xy(79, 8, 'E');
			
			/* the string is external and needs to be copied in */
			do {
				tmp = farreadb(base.segment, base.offset);
				info->oem_data[oemIdx] = tmp;
				oemIdx++;
				base.offset++;
			} while (tmp);
		}
	}
	
	return resp;
}

void putc_xy(int x, int y, char c) {
	farstorew(0xb800, (int *) (y*80*2 + x*2), 0x0200 | c);
}

void __cstartup(void) {
	main();
}

#asm
! fetch VBE controller info
.define _vbeControllerInfo
_vbeControllerInfo:
	push bp
	mov bp,sp
	mov ax,ds					! make sure es is sane
	mov es,ax
	push di
	mov di,[bp+4]				! load address of structure
	mov dword [di],#$32454256	! signature field = 'VBE2'
	mov ax,#$4f00				! VBE function 0x00, get controller info
	int $10
	pop di
	pop bp
	ret
#endasm

#asm
! fetch mode info for mode number
.define _vbeModeInfo
_vbeModeInfo:
	push bp
	mov bp,sp
	mov ax,ds					! make sure es is sane
	mov es,ax
	push di
	mov cx,[bp+4]				! mode number
	mov di,[bp+6]				! address of destination buffer
	mov ax,#$4f01				! VBE function 0x01, get mode info
	int $10
	pop di
	pop bp
	ret
#endasm

#asm
! set mode with mode number and CRTC info block
.define _vbeSetMode
_vbeSetMode:
	push bp
	mov bp,sp
	push bx
	mov ax,ds					! make sure es is sane
	mov es,ax
	push di
	mov bx,[bp+4]				! packed mode number and settings bits
	mov di,[bp+6]				! address of CRTC block
	mov ax,#$4f02				! VBE function 0x02, set mode
	int $10
	pop di
	pop bx
	pop bp
	ret
#endasm

#asm
! get/set display start
.define _vbeDisplayStart
_vbeDisplayStart:
	push bp
	mov bp,sp
	push bx
	push di
	
	xor bh,bh					! bh reserved
	mov bl,[bp+4]				! function
	mov ecx,[bp+5]				! start address / first pixel
	mov edx,[bp+9]				! start address / first scan line
	mov ax,#$4f07				! VBE function 0x07, get/set display start
	int $10
	
	mov di,[bp+13]				! address of return message buffer
	mov [di],bh					! move the return information into the buffer
	mov [di+1],cx
	mov [di+3],dx
	
	pop di
	pop bx
	pop bp
	ret
#endasm

#asm
! query DCC capabilities and EDID info
.define _vbeDccInfo
_vbeDccInfo:
	push bp
	mov bp,sp
	push bx
	push di
	push es
	
	mov bl,[bp+4]				! function
	mov cx,[bp+6]				! controller
	mov ax,#$4f15				! VBE function 0x15, get DCC caps / EDID block
	
	cmp bl,#0					! select function
	jne read_edid
	
								! report VBE/DCC capabilities
	xor di,di					! es:di = 00:00
	mov es,di
	int $10
	
	mov di,[bp+10]				! address of the return message buffer
	mov [di],bx					! bh = transfer time, bl = DCC level supported
	
	jmp continue
	
read_edid:						! get EDID block
	mov dx,ds
	mov es,dx
	mov di,[bp+10]				! es:di = return buffer for block
	mov dx,[bp+8]				! EDID block number
	int $10

continue:
	pop es
	pop di
	pop bx
	pop bp
	ret
#endasm
	
#asm
! reset es to ds
.define _resetES
_resetES:
	mov ax,ds
	mov es,ax
	ret
#endasm
